package com.core.springSecurity.service;


import com.common.utils.Constants;
import com.common.ErrorCodeEnum;
import com.common.redis.RedisCache;
import com.common.utils.AddressUtils;
import com.common.utils.NetworkUtils;
import com.common.utils.ServletUtils;
import com.common.utils.StringUtils;
import com.common.exception.BusinessException;
import com.core.springSecurity.authentication.PasswordAuthenticationToken;
import com.core.springSecurity.authentication.SmsCodeAuthenticationToken;
import com.core.springSecurity.authentication.WXAuthenticationToken;
import com.core.springSecurity.common.FingerprintUtils;
import com.core.springSecurity.model.LoginUserDetails;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 登录校验方法
 *
 *
 */
@Slf4j
@Component
public class SysLoginService {
    @Resource
    private TokenService tokenService;
    @Resource
    private AuthenticationManager authenticationManager;




    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @return 结果
     */
    public String login(String username, String password){
       String id = FingerprintUtils.getFingerprintDeviceID();
       //检查短时间内登录是否过多
       if(!checkLoginRate(id)){
           throw new BusinessException(ErrorCodeEnum.LOGIN_TOO_MUCH.setParam(String.valueOf(tooMuchCD)));
       }

        // 用户验证
        Authentication authentication;
        try {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new PasswordAuthenticationToken(username, password));
        } catch (BusinessException e) {
            recordLoginFailed(id);
            if(!checkLoginRate(id)){
                throw new BusinessException(ErrorCodeEnum.LOGIN_TOO_MUCH.setParam(String.valueOf(tooMuchCD)));
            }
            throw e;
        } catch (Exception e) {
            log.error("未知错误:{}", e.getMessage());
            throw new BusinessException(ErrorCodeEnum.FAILED);
        }

        if (StringUtils.isNull(authentication)) {
            log.error("账号校验失败未知错误 username:{},password:{}", username, password);
            throw new BusinessException(ErrorCodeEnum.USER_CHECK_FAILED);
        }
        //登录成功，清空登录次数
        clearLoginFailed(id);

        LoginUserDetails loginUserDetails = (LoginUserDetails) authentication.getPrincipal();

        remarkUserDetails(loginUserDetails);
        // 生成token
        return tokenService.createToken(loginUserDetails);
    }


    public String login(String username, String password,Long userId){
        String id = FingerprintUtils.getFingerprintDeviceID();
        //检查短时间内登录是否过多
        if(!checkLoginRate(id)){
            throw new BusinessException(ErrorCodeEnum.LOGIN_TOO_MUCH.setParam(String.valueOf(tooMuchCD)));
        }

        // 用户验证
        Authentication authentication;
        try {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new PasswordAuthenticationToken(username, password));
        } catch (BusinessException e) {
            recordLoginFailed(id);
            if(!checkLoginRate(id)){
                throw new BusinessException(ErrorCodeEnum.LOGIN_TOO_MUCH.setParam(String.valueOf(tooMuchCD)));
            }
            throw e;
        } catch (Exception e) {
            log.error("未知错误:{}", e.getMessage());
            throw new BusinessException(ErrorCodeEnum.FAILED);
        }

        if (StringUtils.isNull(authentication)) {
            log.error("账号校验失败未知错误 username:{},password:{}", username, password);
            throw new BusinessException(ErrorCodeEnum.USER_CHECK_FAILED);
        }
        //登录成功，清空登录次数
        clearLoginFailed(id);

        LoginUserDetails loginUserDetails = (LoginUserDetails) authentication.getPrincipal();

        remarkUserDetails(loginUserDetails);
        if(userId!=null){
//            UserBaseInfo userBaseInfo = userBaseInfoService.getById(userId);
//            loginUserDetails.setUserBaseInfo(userBaseInfo);
        }
        // 生成token
        return tokenService.createToken(loginUserDetails);
    }

    /**
     * 登录验证
     *
     * @param mobile  手机号
     * @param smsCode 验证码
     * @return 结果
     */
    public String smsLogin(String mobile, String smsCode) {
        String id = FingerprintUtils.getFingerprintDeviceID();
        //检查短时间内登录是否过多
        if(!checkLoginRate(id)){
            throw new BusinessException(ErrorCodeEnum.LOGIN_TOO_MUCH.setParam(String.valueOf(tooMuchCD)));
        }
        // 用户验证
        Authentication authentication ;
        try {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new SmsCodeAuthenticationToken(mobile, smsCode));
        } catch (BusinessException e) {
            recordLoginFailed(id);
            if(!checkLoginRate(id)){
                throw new BusinessException(ErrorCodeEnum.LOGIN_TOO_MUCH.setParam(String.valueOf(tooMuchCD)));
            }
            throw e;
        } catch (Exception e) {
            log.error("未知错误:{}", e.getMessage());
            throw new BusinessException(ErrorCodeEnum.FAILED);
        }

        if (StringUtils.isNull(authentication)) {
            log.error("账号校验失败未知错误 mobile:{},smsCode:{}", mobile, smsCode);
            throw new BusinessException(ErrorCodeEnum.USER_CHECK_FAILED);
        }

        //登录成功，清空登录次数
        clearLoginFailed(id);

        LoginUserDetails loginUserDetails = (LoginUserDetails) authentication.getPrincipal();

        remarkUserDetails(loginUserDetails);
        return tokenService.createToken(loginUserDetails);
    }

    /**
     * 登录验证
     *
     * @param mobile  手机号
     * @param smsCode 验证码
     * @return 结果
     */
    public String smsNewLogin(String mobile, String smsCode) {
        String id = FingerprintUtils.getFingerprintDeviceID();
        //检查短时间内登录是否过多
        if(!checkLoginRate(id)){
            throw new BusinessException(ErrorCodeEnum.LOGIN_TOO_MUCH.setParam(String.valueOf(tooMuchCD)));
        }
        // 用户验证
        Authentication authentication ;
        try {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new SmsCodeAuthenticationToken(mobile, smsCode));
        } catch (BusinessException e) {
            recordLoginFailed(id);
            if(!checkLoginRate(id)){
                throw new BusinessException(ErrorCodeEnum.LOGIN_TOO_MUCH.setParam(String.valueOf(tooMuchCD)));
            }
            throw e;
        } catch (Exception e) {
            log.error("未知错误:{}", e.getMessage());
            throw new BusinessException(ErrorCodeEnum.FAILED);
        }

        if (StringUtils.isNull(authentication)) {
            log.error("账号校验失败未知错误 mobile:{},smsCode:{}", mobile, smsCode);
            throw new BusinessException(ErrorCodeEnum.USER_CHECK_FAILED);
        }

        //登录成功，清空登录次数
        clearLoginFailed(id);

        LoginUserDetails loginUserDetails = (LoginUserDetails) authentication.getPrincipal();

        remarkUserDetails(loginUserDetails);
        return tokenService.createNurseryToken(loginUserDetails);
    }

    /**
     * 微信登录验证
     *
     * @param openid  用户openid
     * @param
     * @return 结果
     */
    public String wxLogin(String openid) {
        String id = FingerprintUtils.getFingerprintDeviceID();
        //检查短时间内登录是否过多
        if(!checkLoginRate(id)){
            throw new BusinessException(ErrorCodeEnum.LOGIN_TOO_MUCH.setParam(String.valueOf(tooMuchCD)));
        }
        // 用户验证
        Authentication authentication ;
        try {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new WXAuthenticationToken(openid, "111"));
        } catch (BusinessException e) {
            recordLoginFailed(id);
            if(!checkLoginRate(id)){
                throw new BusinessException(ErrorCodeEnum.LOGIN_TOO_MUCH.setParam(String.valueOf(tooMuchCD)));
            }
            throw e;
        } catch (Exception e) {
            log.error("未知错误:{}", e.getMessage());
            throw new BusinessException(ErrorCodeEnum.FAILED);
        }

        if (StringUtils.isNull(authentication)) {
            log.error("账号校验失败未知错误 mobile:{},smsCode:{}", openid, "");
            throw new BusinessException(ErrorCodeEnum.USER_CHECK_FAILED);
        }

        //登录成功，清空登录次数
        clearLoginFailed(id);

        LoginUserDetails loginUserDetails = (LoginUserDetails) authentication.getPrincipal();

        remarkUserDetails(loginUserDetails);
        return tokenService.createNurseryToken(loginUserDetails);
    }

    private void remarkUserDetails(LoginUserDetails loginUserDetails){
        final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        final String ip = NetworkUtils.getIPAddress(ServletUtils.getRequest());
        String address = AddressUtils.getRealAddressByIp(ip);
        // 获取客户端操作系统
        String os = userAgent.getOperatingSystem().getName();
        // 获取客户端浏览器
        String browser = userAgent.getBrowser().getName();

        loginUserDetails.setOs(os);
        loginUserDetails.setBrowser(browser);
        loginUserDetails.setLoginLocation(address);
        loginUserDetails.setIpaddr(ip);
    }


    @Resource
    RedisCache redisCache;
    /**登录次数记录时间(单位：分钟)*/
    @Value("${login.tooMuchCD:1}")
    private int tooMuchCD;
    /**登录次数达到n次限制登录*/
    @Value("${login.threshold:30}")
    private int threshold;

    /**
     * @Author: zql
     * @Description: 校验登录失败次数是否过多
     * @param id 用户的唯一id
     * @Data: 16:15 2021/7/13
     */
    public boolean checkLoginRate(String id) {
        Integer loginFrequency = redisCache.getCacheObject(getFingerprintKey(id));
        if(!(StringUtils.isNotNull(loginFrequency) && loginFrequency >= threshold)){
            return true;
        }
        log.debug("登录次数过多");
        return false;
    }

    /**
     * @Author: zql
     * @Description: 记录并刷新登录失败次数
     * @param id 用户的唯一id
     * @Data: 17:07 2021/7/13
     */
    public void recordLoginFailed(String id){
        String key= getFingerprintKey(id);
        Integer loginFrequency = redisCache.getCacheObject(getFingerprintKey(id));
        if(StringUtils.isNull(loginFrequency)){
            loginFrequency = 0;
        }
        loginFrequency++;
        log.debug("登录失败，登录次数记录+1 key:{},frequency:{}",key,loginFrequency);
        redisCache.setCacheObject(key, loginFrequency, tooMuchCD, TimeUnit.MINUTES);
    }

    /**
     * @Author: zql
     * @Description: 清空登录失败
     * @param id 用户的唯一id
     * @Data: 17:07 2021/7/13
     */
    public void clearLoginFailed(String id){
        //删除登录记录
        String key= getFingerprintKey(id);
        redisCache.deleteObject(key);
    }

    /**
     * @Author: zql
     * @Description: 获取登录次数记录在redis的key
     * @param id 用户的唯一id
     * @Data: 17:08 2021/7/13
     */
    public String getFingerprintKey(String id){
        return Constants.FINGERPRINT_LOGIN_FREQUENCY_PREFIX + id;
    }
}
